//=============================================================================
/**
 * @file	gym_poison.c
 * @brief	毒ジムギミック
 * @author	Nozomu Saito
 *
 * モジュール名：GYM_
 */
//=============================================================================

#include "common.h"
#include "fieldsys.h"
#include "fieldmap_work.h"
#include "system/snd_tool.h"
#include "include/savedata/gimmickwork.h"
#include "../fielddata/build_model/build_model_id.h"

#include "field_gimmick_def.h"
#include "sysflag_def.h"

#include "gym_def.h"

#include "gym_local.h"
#include "gym_init.h"

#include "gym.h"

#define PSN_WALL_ANM_NUM	(4)
#define PSN_WALL_TYPE_NUM	(12)
#define WALL_TCB_NUM	(3)

//↓アーカイブインデックスに依存
//アーカイブは、imd x 12 ica x 12 ima x 12 ita x 12 itp x 12 の順番で格納されているとみなす
#define ARC_MLD_BASE	(0)
#define ARC_ANM_BASE	(12)

#define WALL_SLOT_NUM	(3)		//最大で、平行して3つ壁アニメ再生ができる

//壁データ
typedef struct PSN_WALL_tag
{
	BOOL Valid;
	FLD_3DOBJ_ANM	Anm[PSN_WALL_ANM_NUM];
	FLD_3DOBJ		Obj;
}PSN_WALL;

//壁スタックデータ
typedef struct PSN_WALL_STACKtag
{
	int SlotIdx;
	PSN_WALL GraDat[WALL_SLOT_NUM];
}PSN_WALL_STACK;

//TCBパック
typedef struct WALL_TCB_tag
{
	u16 Valid;
	s16 WallNo;
	PSN_WALL *WallPtr;
	TCB_PTR Tcb;
}WALL_TCB;

typedef struct POISON_GYM_WALL_tag
{
	int X;
	int Z;
	u16 ChipNo;
	s8	OfsX;
	s8	OfsZ;

	s8 CheckNo[6];
	u8 dummy[2];
}POISON_GYM_WALL;

typedef struct POISON_GYM_TEMP_WORK_tag
{
	NNSFndAllocator Allocator;
	FLD_3DOBJ_MDL	WallMdl[PSN_WALL_TYPE_NUM];
	int TcbIdx;

	PSN_WALL_STACK WallStack[PSN_WALL_TYPE_NUM];

	WALL_TCB	WallTcb[WALL_TCB_NUM];
}POISON_GYM_TEMP_WORK;

#include "psn_gym_wall.dat"

static void EffTcb( TCB_PTR tcb, void *work );

static void SetupWallGraphic(POISON_GYM_TEMP_WORK *work, const int inHeapID);
static void DelWallGraphic(POISON_GYM_TEMP_WORK *work);

static s16 SearchWallNo(const int inX, const int inZ);
static BOOL IsWallTcb(WALL_TCB *inWallTcb, const int inWallNo);
static void SetWallTcb(POISON_GYM_TEMP_WORK *work, FIELDSYS_WORK *fsys, const u8 inWallNo);
static BOOL AnimeNoLoop(FLD_3DOBJ_ANM *anm, const u8 inNum);
//static void GetHitPos(FIELDSYS_WORK *fsys, int *outX, int *outZ);
static PSN_WALL *GetNonUseWallGraDat(PSN_WALL_STACK *stack);
static void CancelWallTcb(POISON_GYM_TEMP_WORK *work, const u8 inWallNo);
static void ClearWallTcb(WALL_TCB *wall_tcb);

//---------------------------------------------------------------------------
/**
 * @brief	毒ジム:セットアップ
 *
 * @param	fsys	フィールドシステムポインタ
 *
 * @return	none
 */
//---------------------------------------------------------------------------
void GYM_SetupPoisonGym(FIELDSYS_WORK *fsys)
{
	int i;
	GIMMICKWORK *work;
	POISON_GYM_WORK *poison_gym_work;
	POISON_GYM_TEMP_WORK *poison_temp;

	OS_Printf("毒ジムセットアップ\n");

	//ここでギミックワークの中身を見る
	work = SaveData_GetGimmickWork(GameSystem_GetSaveData(fsys));
	poison_gym_work = (POISON_GYM_WORK*)GIMMICKWORK_Get(work, FLD_GIMMICK_POISON_GYM);

	//毒ジム専用ワークをアロケートして、フィールドマップ汎用ワークポインタにあてる
	fsys->fldmap->Work = sys_AllocMemory(HEAPID_FIELD, sizeof(POISON_GYM_TEMP_WORK));
	//初期化
	MI_CpuClear8(fsys->fldmap->Work, sizeof(POISON_GYM_TEMP_WORK));
	poison_temp = fsys->fldmap->Work;

	//使用する全壁パーツをセットアップ
	SetupWallGraphic(poison_temp, HEAPID_FIELD);

	//壁TCBの壁ナンバーに-1をセット
	for(i=0;i<WALL_TCB_NUM;i++){
		poison_temp->WallTcb[i].WallNo = -1;
	}
}

//---------------------------------------------------------------------------
/**
 * @brief	毒ジム:終了処理
 *
 * @param	fsys	フィールドシステムポインタ
 *
 * @return	none
 */
//---------------------------------------------------------------------------
void GYM_EndPoisonGym(FIELDSYS_WORK *fsys)
{
	u8 i;
	POISON_GYM_TEMP_WORK *poison_temp;
	poison_temp = (POISON_GYM_TEMP_WORK *)fsys->fldmap->Work;

	//グラフィックデータ削除
	DelWallGraphic(poison_temp);

	//TCBが起動している場合は削除する
	for (i=0;i<WALL_TCB_NUM;i++){
		if ( poison_temp->WallTcb[i].Valid ){
			OS_Printf("壁TCB削除\n");
			TCB_Delete(poison_temp->WallTcb[i].Tcb);
		}
	}
	sys_FreeMemoryEz(poison_temp);
	fsys->fldmap->Work = NULL;
}

//---------------------------------------------------------------------------
/**
 * @brief	毒ジム:壁に当たったときのエフェクト発生処理
 *
 * @param	fsys	フィールドシステムポインタ
 * @param	inX		ヒットグリッドX座標
 * @param	inZ		ヒットグリッドZ座標
 *
 * @return	none
 */
//---------------------------------------------------------------------------
void GYM_SetPoisonGymEff(FIELDSYS_WORK *fsys, const int inX, const int inZ)
{
	POISON_GYM_TEMP_WORK *poison_temp;
	s16 wall_no ;
	poison_temp = (POISON_GYM_TEMP_WORK *)fsys->fldmap->Work;

	//ヒット座標検出
///	GetHitPos(fsys, &x, &z);

	OS_Printf("hitpos = %d,%d\n",inX,inZ);

	//ヒットした座標から壁ナンバーを特定する
	wall_no = SearchWallNo(inX, inZ);

	if (wall_no < 0){
		Snd_SePlay( SEQ_SE_DP_WALL_HIT );
		return;
	}
	//特定した壁ナンバーのアニメがアニメTCBで既に稼動している場合は処理しない
	if ( IsWallTcb(poison_temp->WallTcb, wall_no) ){
		return;
	}

	OS_Printf("エフェクトTCBをセット　壁ナンバー%d\n",wall_no);

	//未使用壁TCBに新規セット
	SetWallTcb(poison_temp, fsys, wall_no);
	Snd_SePlay( SEQ_SE_GS_TOUMEINAKABEHIT );
}

//---------------------------------------------------------------------------
/**
 * @brief	毒ジム:壁アニメTCB
 *
 * @param	tcb		TCBポインタ
 * @param	work	ワークポインタ
 *
 * @return	none
 */
//---------------------------------------------------------------------------
static void EffTcb( TCB_PTR tcb, void *work )
{
	u16 wall_no;
	PSN_WALL *wall_ptr;
	WALL_TCB *wall_tcb = (WALL_TCB *)work;

	OS_Printf("壁TCB稼働中\n");

	wall_no = wall_tcb->WallNo;
	wall_ptr = wall_tcb->WallPtr;

	if ( AnimeNoLoop( wall_ptr->Anm, PSN_WALL_ANM_NUM ) ){
		OS_Printf("壁TCB終了\n");
		ClearWallTcb(wall_tcb);
		return;
	}

	FLD_3DObjDraw( &wall_ptr->Obj );
}

//---------------------------------------------------------------------------
/**
 * @brief	壁グラフィックセットアップ
 *
 * @param	work		ワーク
 * @param	inHeapID	ヒープID
 *
 * @return	none
 */
//---------------------------------------------------------------------------
static void SetupWallGraphic(POISON_GYM_TEMP_WORK *work, const int inHeapID)
{
	u8 i;
	// アロケーター作成
	sys_InitAllocator( &work->Allocator, inHeapID, 32 );

	for (i=0;i<PSN_WALL_TYPE_NUM;i++){
		PSN_WALL *wall;
		u8 s;
		work->WallStack[i].SlotIdx = 0;		//参照開始スロットインデックス初期化
		//モデルロード
		FLD_3DObjMdlLoad(&work->WallMdl[i], ARC_GYM_PSN_GRA , ARC_MLD_BASE+i, inHeapID);
		//スロット分ループ
		for (s=0;s<WALL_SLOT_NUM;s++){
			u8 j;
			wall = &work->WallStack[i].GraDat[s];
			wall->Valid = FALSE;
			//モデルロード
///			FLD_3DObjMdlLoad(&wall[i].Mdl, ARC_GYM_PSN_GRA , ARC_MLD_BASE+i, inHeapID);
			//アニメデータロード
			for(j=0;j<PSN_WALL_ANM_NUM;j++){
				FLD_3DObjAnmLoad(	&wall->Anm[j], &work->WallMdl[i], ARC_GYM_PSN_GRA,
									ARC_ANM_BASE+(j*PSN_WALL_TYPE_NUM)+i,
									inHeapID, &work->Allocator );
			}
			//描画オブジェクト初期化
			FLD_3DObjInit( &wall->Obj, &work->WallMdl[i] );

			for(j=0;j<PSN_WALL_ANM_NUM;j++){
				//アニメをリンク
				FLD_3DObjAddAnm( &wall->Obj, &wall->Anm[j] );
				//フレームセット
				FLD_3DObjAnmSet( &wall->Anm[j], 0 );
			}

			//座標セット
			{
				VecFx32 vec = {0,0,0};
				FLD_3DObjSetMatrix( &wall->Obj, vec.x, vec.y, vec.z );
			}

			//描画フラグ設定	(非表示)
			FLD_3DObjSetDraw( &wall->Obj, FALSE );
		}
	}
}

//---------------------------------------------------------------------------
/**
 * @brief	壁グラフィック削除
 *
 * @param	work			ワーク
 *
 * @return	none
 */
//---------------------------------------------------------------------------
static void DelWallGraphic(POISON_GYM_TEMP_WORK *work)
{
	u8 i,j,s;

	for (i=0;i<PSN_WALL_TYPE_NUM;i++){
		PSN_WALL *wall;
		//モデルデータ破棄
		FLD_3DObjMdlDelete( &work->WallMdl[i]);
		for (s=0;s<WALL_SLOT_NUM;s++){
			wall = &work->WallStack[i].GraDat[s];
			for(j=0;j<PSN_WALL_ANM_NUM;j++){
				//アニメデータ破棄
				FLD_3DObjAnmDelete( &wall->Anm[j], &work->Allocator);
			}
			//モデルデータ破棄
///			FLD_3DObjMdlDelete( &wall->Mdl);
		}
	}
}

//---------------------------------------------------------------------------
/**
 * @brief	座標から壁ナンバーを検索
 *
 * @param	inX		Xグリッド座標
 * @param	inZ		Zグリッド座標
 *
 * @return	no		壁ナンバー（0オリジン）-1が返った場合は検索失敗
 */
//---------------------------------------------------------------------------
static s16 SearchWallNo(const int inX, const int inZ)
{
	s16 i;
	for (i=0;i<WALL_MAX;i++){
		if ( (WallDat[i].X == inX) && (WallDat[i].Z == inZ) ){
			return i;
		}
	}
	return -1;
}

//---------------------------------------------------------------------------
/**
 * @brief	指定壁ナンバーを持つ壁TCBが存在するかを調べる
 *
 * @param	inWallTcb		壁TCB先頭アドレス
 * @param	inWallNo		壁ナンバー（0オリジン）
 *
 * @return	BOOL			TRUE:存在している	FALSE:存在していない
 */
//---------------------------------------------------------------------------
static BOOL IsWallTcb(WALL_TCB *inWallTcb, const int inWallNo)
{
	u8 i;
	for (i=0;i<WALL_TCB_NUM;i++){
		if ( (inWallTcb[i].Valid)&&(inWallTcb[i].WallNo == inWallNo) ){
			return TRUE;
		}
	}

	return FALSE;
}

//---------------------------------------------------------------------------
/**
 * @brief	壁TCBを新規セット
 *
 * @param	work		ジムワークポインタ
 * @param	fsys		フィールドシステムポインタ
 * @param	inWallNo	壁ナンバー（0オリジン）
 *
 * @return	none
 */
//---------------------------------------------------------------------------
static void SetWallTcb(POISON_GYM_TEMP_WORK *work, FIELDSYS_WORK *fsys, const u8 inWallNo)
{
	u8 i;
	u8 chip_no;
	WALL_TCB *wall_tcb;
	wall_tcb = &work->WallTcb[ work->TcbIdx ];
	if (wall_tcb->Valid){
		GF_ASSERT_MSG(0,"POISON_GYM_ERROR:TCBが足りてません\n");
		return;
	}

	//アニメ影響のある他の壁TCBを終了させる
	CancelWallTcb(work, inWallNo);

	wall_tcb->Valid = 1;	//使用中
	wall_tcb->WallNo = inWallNo;	//壁ナンバー保存
	chip_no = WallDat[inWallNo].ChipNo;
	OS_Printf("チップナンバー%d\n",chip_no);

	//スロットから空いている壁グラフィックデータポインタを取得
	wall_tcb->WallPtr = GetNonUseWallGraDat(&work->WallStack[chip_no]);

	wall_tcb->Tcb = TCB_Add(EffTcb, wall_tcb, 0);
	//壁表示
	FLD_3DObjSetDraw( &wall_tcb->WallPtr->Obj, TRUE );
	//壁表示座標をセット
	{
		VecFx32 vec = {0,0,0};
		FieldOBJ_VecPosGet( Player_FieldOBJGet( fsys->player ), &vec );
		vec.x = ( GRID_HALF + (WallDat[inWallNo].X * GRID) + (WallDat[inWallNo].OfsX * GRID) ) * FX32_ONE;
		vec.z = ( GRID_HALF + (WallDat[inWallNo].Z * GRID) + (WallDat[inWallNo].OfsZ * GRID) ) * FX32_ONE;
		FLD_3DObjSetMatrix( &wall_tcb->WallPtr->Obj, vec.x, vec.y, vec.z );
	}
	//フレームセット
	for(i=0;i<PSN_WALL_ANM_NUM;i++){
		FLD_3DObjAnmSet( &wall_tcb->WallPtr->Anm[i], 0 );
	}

	//壁TCBインデックス更新
	work->TcbIdx = (work->TcbIdx+1) % WALL_TCB_NUM;
}

//==============================================================================
/**
 * アニメフレーム更新関数
 *
 * @param	anm			アニメポインタ配列
 * @param	inNum		アニメ数
 *
 * @retval  BOOL		TRUE：アニメ終了	FALSE：アニメ継続
 */
//==============================================================================
static BOOL AnimeNoLoop(FLD_3DOBJ_ANM *anm, const u8 inNum)
{
	u8 i;
	BOOL end;
	u8 end_num = 0;
	for (i=0;i<inNum;i++){
		end = FLD_3DObjAnmNoLoop( &anm[i], FX32_ONE );
		if (end){
			OS_Printf("%d:アニメ終了\n",i);
			end_num++;
		}
	}
	if (end_num == inNum){
		return TRUE;
	}

	return FALSE;
}

//==============================================================================
/**
 * スロットから未使用のグラフィックデータポインタを取得
 *
 * @param	stack			壁スタックデータポインタ
 *
 * @retval  none
 */
//==============================================================================
static PSN_WALL *GetNonUseWallGraDat(PSN_WALL_STACK *stack)
{
	PSN_WALL *ret_dat;
	if (stack->GraDat[ stack->SlotIdx ].Valid){
		GF_ASSERT_MSG(0,"POISN_GYM_ERROR:スロットが足りない\n");
	}

	ret_dat = &stack->GraDat[ stack->SlotIdx ];

	stack->GraDat[ stack->SlotIdx ].Valid = TRUE;	//使用中にする

	//参照インデックス更新
	stack->SlotIdx = (stack->SlotIdx + 1) % WALL_SLOT_NUM;

	return ret_dat;

}

//==============================================================================
/**
 * アニメ影響のある他の壁TCBを終了させる
 *
 * @param	work		ワーク
 * @param	inWallNo	壁ナンバー
 *
 * @retval  none
 */
//==============================================================================
static void CancelWallTcb(POISON_GYM_TEMP_WORK *work, const u8 inWallNo)
{
	u8 i,j;
	OS_Printf("ヒット壁ナンバー　%d\n",inWallNo);
	//チェック回数分ループ
	for(i=0;i<CHECK_NUM_MAX;i++){
		if ( WallDat[inWallNo].CheckNo[i] < 0 ){	//チェック配列終端コード（-1）検出で終了
			break;
		}
		OS_Printf("チェック壁ナンバー %d\n",WallDat[inWallNo].CheckNo[i]);

		//管理TCB分ループ
		for (j=0;j<WALL_TCB_NUM;j++){
			s16 wall_no;
			//使用中チェック
			if (!work->WallTcb[j].Valid){	//未使用なら処理を飛ばす
				continue;
			}
			//壁TCBの壁ナンバーセット
			wall_no = work->WallTcb[j].WallNo;
			OS_Printf("稼動壁ナンバー　%d\n",wall_no);
			if( WallDat[inWallNo].CheckNo[i] == wall_no ){	//発見
				OS_Printf("キャンセルする壁を発見　壁No:%d\n",wall_no);
				ClearWallTcb(&work->WallTcb[j]);
				break;	//壁TCB群には同一の壁ナンバーははいらない（クリア時の-1は別）ので、次のチェックに移る
			}
		}
	}
}

//==============================================================================
/**
 * 壁TCBクリア
 *
 * @param	wall_tcb		壁TCB
 *
 * @retval  none
 */
//==============================================================================
static void ClearWallTcb(WALL_TCB *wall_tcb)
{
	//対象モデルをバニッシュ
	FLD_3DObjSetDraw( &wall_tcb->WallPtr->Obj, FALSE );
	//未使用状態にする
	wall_tcb->WallPtr->Valid = FALSE;
	//壁TCBフラグを落とす
	wall_tcb->Valid = 0;
	//壁ナンバーを-1にしておく
	wall_tcb->WallNo = -1;
	//壁グラフィックデータ参照終了
	wall_tcb->WallPtr = NULL;
	//TCBを削除
	TCB_Delete(wall_tcb->Tcb);
	wall_tcb->Tcb = NULL;
}